package terra.model.object.knownlist;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.Serializable;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ScheduledFuture;

import terra.managers.ThreadPoolManager;
import terra.model.World;
import terra.model.object.GameObject;

public class ObjectKnownList implements Serializable {
	/**
	 * 
	 */
	private static final long serialVersionUID = -5068270440961929318L;
	protected GameObject owner;
	private Map<Integer, GameObject> knownObjects;

	public int knowDistance = 1500;
	public transient ScheduledFuture<?> updateTask;
	private boolean autoUpdate = false;
	private int autoUpdateTime = 500;

	public ObjectKnownList(GameObject obj) {
		owner = obj;
	}

	public boolean addKnownObject(GameObject object) {
		if (object == null || object == getOwner()) {
			return false;
		}
		if (knowsObject(object)) {
			return false;
		}
		if (object.distanceTo(getOwner()) > knowDistance) {
			return false;
		}
		getKnownObjects().put(object.getObjectId(), object);
		return true;
	}

	public final boolean knowsObject(GameObject object) {
		return getOwner() == object
				|| getKnownObjects().containsKey(object.getObjectId());
	}

	public void removeAllKnownObjects() {
		getKnownObjects().clear();
	}

	public boolean removeKnownObject(GameObject object) {
		if (object == null)
			return false;

		return (getKnownObjects().remove(object.getObjectId()) != null);
	}

	public final synchronized void updateKnownObjects() {
		try {
			findCloseObjects();
			forgetObjects();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	private final void findCloseObjects() {
		for (GameObject obj : World.getInstance().getObjects().values()) {
			if (obj == null)
				continue;
			if (obj == owner)
				continue;
			addKnownObject(obj);
			if (obj.getKnownList() != null)
				obj.getKnownList().addKnownObject(getOwner());
		}
	}

	private final void forgetObjects() {
		if (knownObjects == null || knownObjects.size() == 0)
			return;
		for (GameObject obj : getKnownObjects().values()) {
			if (obj.distanceTo(getOwner()) > knowDistance)
				removeKnownObject(obj);
			if (obj.isRemove())
				removeKnownObject(obj);
		}
	}

	public GameObject getOwner() {
		return owner;
	}

	public void setOwner(GameObject owner) {
		this.owner = owner;
	}

	public Map<Integer, GameObject> getKnownObjects() {
		if (knownObjects == null)
			knownObjects = new ConcurrentHashMap<Integer, GameObject>();
		return knownObjects;
	}

	public void setKnownObjects(Map<Integer, GameObject> knownObjects) {
		this.knownObjects = knownObjects;
	}

	public void setAutoUpdate(int time) {
		if (updateTask == null) {
			updateTask = ThreadPoolManager.getInstance()
					.scheduleGeneralAtFixedRate(
							new KnownListAsynchronousUpdateTask(owner), time,
							time);
			autoUpdate = true;
			autoUpdateTime = time;
		}
	}

	public void removeAutoUpdate() {
		if (updateTask != null) {
			updateTask.cancel(true);
			autoUpdate = false;
		}
	}

	public static class KnownListAsynchronousUpdateTask implements Runnable {
		private GameObject _obj;

		public KnownListAsynchronousUpdateTask(GameObject obj) {
			_obj = obj;
		}

		public void run() {
			if (_obj != null && !_obj.isRemove()) {
				if (World.getInstance().getUpdateObjects()
						.containsKey(_obj.getObjectId()))
					_obj.getKnownList().updateKnownObjects();
			} else
				ThreadPoolManager.getInstance().removeRunnable(this);
		}
	}

	private void readObject(ObjectInputStream in) throws IOException,
			ClassNotFoundException {
		in.defaultReadObject();
		if (autoUpdate)
			setAutoUpdate(autoUpdateTime);
	}
}
